;**** A P P L I C A T I O N   N O T E   A V R 2 0 4 ************************
;*
;* Title:		BCD Arithmetics
;* Version:		1.1
;* Last updated:	97.07.04
;* Target:		AT90Sxxxx (All AVR Devices)
;*
;* Support E-mail:	avr@atmel.com
;* 

;* DESCRIPTION
;* This Application Note lists subroutines for the following Binary Coded
;* Decimal arithmetic applications:
;*
;* Binary 16 to BCD Conversion (special considerations for AT90Sxx0x)
;* Binary 8 to BCD Conversion
;* BCD to Binary 16 Conversion
;* BCD to Binary 8 Conversion
;* 2-Digit BCD Addition
;* 2-Digit BCD Subtraction
;*

;***************************************************************************


.include "8515def.inc"

	rjmp	RESET	;reset handle

;***************************************************************************
;*
;* "bin2BCD16" - 16-bit Binary to BCD conversion
;*
;* This subroutine converts a 16-bit number (fbinH:fbinL) to a 5-digit 
;* packed BCD number represented by 3 bytes (tBCD2:tBCD1:tBCD0).
;* MSD of the 5-digit number is placed in the lowermost nibble of tBCD2.
;*  
;* Number of words	:25
;* Number of cycles	:751/768 (Min/Max)
;* Low registers used	:3 (tBCD0,tBCD1,tBCD2) 
;* High registers used  :4(fbinL,fbinH,cnt16a,tmp16a)	
;* Pointers used	:Z
;*
;***************************************************************************

;***** Subroutine Register Variables

.equ	AtBCD0	=13		;address of tBCD0
.equ	AtBCD2	=15		;address of tBCD1

.def	tBCD0	=r13		;BCD value digits 1 and 0
.def	tBCD1	=r14		;BCD value digits 3 and 2
.def	tBCD2	=r15		;BCD value digit 4
.def	fbinL	=r16		;binary value Low byte
.def	fbinH	=r17		;binary value High byte
.def	cnt16a	=r18		;loop counter
.def	tmp16a	=r19		;temporary value

;***** Code

bin2BCD16:
	ldi	cnt16a,16	;Init loop counter	
	clr	tBCD2		;clear result (3 bytes)
	clr	tBCD1		
	clr	tBCD0		
	clr	ZH		;clear ZH (not needed for AT90Sxx0x)
bBCDx_1:lsl	fbinL		;shift input value
	rol	fbinH		;through all bytes
	rol	tBCD0		;
	rol	tBCD1
	rol	tBCD2
	dec	cnt16a		;decrement loop counter
	brne	bBCDx_2		;if counter not zero
	ret			;   return

bBCDx_2:ldi	r30,AtBCD2+1	;Z points to result MSB + 1
bBCDx_3:
	ld	tmp16a,-Z	;get (Z) with pre-decrement
;----------------------------------------------------------------
;For AT90Sxx0x, substitute the above line with:
;
;	dec	ZL
;	ld	tmp16a,Z
;
;----------------------------------------------------------------
	subi	tmp16a,-$03	;add 0x03
	sbrc	tmp16a,3	;if bit 3 not clear
	st	Z,tmp16a	;	store back
	ld	tmp16a,Z	;get (Z)
	subi	tmp16a,-$30	;add 0x30
	sbrc	tmp16a,7	;if bit 7 not clear
	st	Z,tmp16a	;	store back
	cpi	ZL,AtBCD0	;done all three?
	brne	bBCDx_3		;loop again if not
	rjmp	bBCDx_1		



;***************************************************************************
;*
;* "bin2BCD8" - 8-bit Binary to BCD conversion
;*
;* This subroutine converts an 8-bit number (fbin) to a 2-digit 
;* BCD number (tBCDH:tBCDL).
;*  
;* Number of words	:6 + return
;* Number of cycles	:5/50 (Min/Max) + return
;* Low registers used	:None
;* High registers used  :2 (fbin/tBCDL,tBCDH)
;*
;* Included in the code are lines to add/replace for packed BCD output.	
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	fbin	=r16		;8-bit binary value
.def	tBCDL	=r16		;BCD result MSD
.def	tBCDH	=r17		;BCD result LSD

;***** Code

bin2bcd8:
	clr	tBCDH		;clear result MSD
bBCD8_1:subi	fbin,10		;input = input - 10
	brcs	bBCD8_2		;abort if carry set
	inc	tBCDH		;inc MSD
;---------------------------------------------------------------------------
;				;Replace the above line with this one
;				;for packed BCD output				
;	subi	tBCDH,-$10 	;tBCDH = tBCDH + 10
;---------------------------------------------------------------------------
	rjmp	bBCD8_1		;loop again
bBCD8_2:subi	fbin,-10	;compensate extra subtraction
;---------------------------------------------------------------------------
;				;Add this line for packed BCD output
;	add	fbin,tBCDH	
;---------------------------------------------------------------------------	
	ret



;***************************************************************************
;*
;* "BCD2bin16" - BCD to 16-Bit Binary Conversion
;*
;* This subroutine converts a 5-digit packed BCD number represented by 
;* 3 bytes (fBCD2:fBCD1:fBCD0) to a 16-bit number (tbinH:tbinL).
;* MSD of the 5-digit number must be placed in the lowermost nibble of fBCD2.
;* 
;* Let "abcde" denote the 5-digit number. The conversion is done by
;* computing the formula: 10(10(10(10a+b)+c)+d)+e.
;* The subroutine "mul10a"/"mul10b" does the multiply-and-add operation 
;* which is repeated four times during the computation.
;*  
;* Number of words	:30 
;* Number of cycles	:108 
;* Low registers used	:4 (copyL,copyH,mp10L/tbinL,mp10H/tbinH)
;* High registers used  :4 (fBCD0,fBCD1,fBCD2,adder)	
;*
;***************************************************************************

;***** "mul10a"/"mul10b" Subroutine Register Variables

.def	copyL	=r12		;temporary register
.def	copyH	=r13		;temporary register
.def	mp10L	=r14		:Low byte of number to be multiplied by 10
.def	mp10H	=r15		;High byte of number to be multiplied by 10
.def	adder	=r19		;value to add after multiplication	

;***** Code

mul10a:	;***** multiplies "mp10H:mp10L" with 10 and adds "adder" high nibble 
	swap	adder
mul10b:	;***** multiplies "mp10H:mp10L" with 10 and adds "adder" low nibble 
	mov	copyL,mp10L	;make copy
	mov	copyH,mp10H
	lsl	mp10L		;multiply original by 2
	rol	mp10H
	lsl	copyL		;multiply copy by 2
	rol	copyH		
	lsl	copyL		;multiply copy by 2 (4)
	rol	copyH		
	lsl	copyL		;multiply copy by 2 (8)
	rol	copyH		
	add	mp10L,copyL	;add copy to original
	adc	mp10H,copyH	
	andi	adder,0x0f	;mask away upper nibble of adder
	add	mp10L,adder	;add lower nibble of adder
	brcc	m10_1		;if carry not cleared
	inc	mp10H		;	inc high byte
m10_1:	ret	

;***** Main Routine Register Variables

.def	tbinL	=r14		;Low byte of binary result (same as mp10L)
.def	tbinH	=r15		;High byte of binary result (same as mp10H)
.def	fBCD0	=r16		;BCD value digits 1 and 0
.def	fBCD1	=r17		;BCD value digits 2 and 3
.def	fBCD2	=r18		;BCD value digit 5

;***** Code

BCD2bin16:
	andi	fBCD2,0x0f	;mask away upper nibble of fBCD2
	clr	mp10H		
	mov	mp10L,fBCD2	;mp10H:mp10L = a
	mov	adder,fBCD1
	rcall	mul10a		;mp10H:mp10L = 10a+b
	mov	adder,fBCD1
	rcall	mul10b		;mp10H:mp10L = 10(10a+b)+c
	mov	adder,fBCD0		
	rcall	mul10a		;mp10H:mp10L = 10(10(10a+b)+c)+d
	mov	adder,fBCD0
	rcall	mul10b		;mp10H:mp10L = 10(10(10(10a+b)+c)+d)+e
	ret


;***************************************************************************
;*
;* "BCD2bin8" - BCD to 8-bit binary conversion
;*
;* This subroutine converts a 2-digit BCD number (fBCDH:fBCDL) to an 
;* 8-bit number (tbin).
;*  
;* Number of words	:4 + return
;* Number of cycles	:3/48 (Min/Max) + return
;* Low registers used	:None
;* High registers used  :2 (tbin/fBCDL,fBCDH)	
;*
;* Modifications to make the routine accept a packed BCD number is indicated
;* as comments in the code. If the modifications are used, fBCDH shall be
;* loaded with the BCD number to convert prior to calling the routine.
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	tbin	=r16		;binary result
.def	fBCDL	=r16		;lower digit of BCD input
.def	fBCDH	=r17		;higher digit of BCD input

;***** Code

BCD2bin8:
;--------------------------------------------------------------------------
;|				;For packed BCD input, add these two lines
;|	mov	tbin,fBCDH	;copy input to result
;|	andi	tbin,$0f	;clear higher nibble of result
;--------------------------------------------------------------------------

BCDb8_0:subi	fBCDH,1		;fBCDH = fBCDH - 1
	brcs	BCDb8_1		;if carry not set
;--------------------------------------------------------------------------
;|				;For packed BCD input, replace the above
;|				;two lines with these.
;|	subi	fBCDH,$10	;MSD = MSD - 1
;|	brmi	BCDb8_1		;if Zero flag not set
;--------------------------------------------------------------------------
	subi	tbin,-10	;    result = result + 10
	rjmp	BCDb8_0		;    loop again
BCDb8_1:ret			;else return



;***************************************************************************
;*
;* "BCDadd" - 2-digit packed BCD addition
;*
;* This subroutine adds the two unsigned 2-digit BCD numbers 
;* "BCD1" and "BCD2". The result is returned in "BCD1", and the overflow 
;* carry in "BCD2".
;*  
;* Number of words	:19
;* Number of cycles	:17/20 (Min/Max)
;* Low registers used	:None
;* High registers used  :3 (BCD1,BCD2,tmpadd)	
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	BCD1	=r16		;BCD input value #1
.def	BCD2	=r17		;BCD input value #2
.def	tmpadd	=r18		;temporary register

;***** Code

BCDadd:
	ldi	tmpadd,6	;value to be added later
	add	BCD1,BCD2	;add the numbers binary
	clr	BCD2		;clear BCD carry
	brcc	add_0		;if carry not clear
	ldi	BCD2,1		;    set BCD carry
add_0:	brhs	add_1		;if half carry not set
	add	BCD1,tmpadd	;    add 6 to LSD
	brhs	add_2		;    if half carry not set (LSD <= 9)
	subi	BCD1,6		;        restore value
	rjmp	add_2		;else
add_1:	add	BCD1,tmpadd	;    add 6 to LSD
add_2:	swap	tmpadd
	add	BCD1,tmpadd	;add 6 to MSD
	brcs	add_4		;if carry not set (MSD <= 9)
	sbrs	BCD2,0		;    if previous carry not set
	subi	BCD1,$60	;	restore value 
add_3:	ret			;else
add_4:	ldi	BCD2,1		;    set BCD carry
	ret



;***************************************************************************
;*
;* "BCDsub" - 2-digit packed BCD subtraction
;*
;* This subroutine subtracts the two unsigned 2-digit BCD numbers 
;* "BCDa" and "BCDb" (BCDa - BCDb). The result is returned in "BCDa", and 
;* the underflow carry in "BCDb".
;*  
;* Number of words	:13
;* Number of cycles	:12/17 (Min/Max)
;* Low registers used	:None
;* High registers used  :2 (BCDa,BCDb)	
;*
;***************************************************************************

;***** Subroutine Register Variables

.def	BCDa	=r16		;BCD input value #1
.def	BCDb	=r17		;BCD input value #2

;***** Code

BCDsub:
	sub	BCDa,BCDb	;subtract the numbers binary
	clr	BCDb
	brcc	sub_0		;if carry not clear
	ldi	BCDb,1		;    store carry in BCDB1, bit 0
sub_0:	brhc	sub_1		;if half carry not clear
	subi	BCDa,$06	;    LSD = LSD - 6
sub_1:	sbrs	BCDb,0		;if previous carry not set
	ret			;    return
	subi	BCDa,$60	;subtract 6 from MSD
	ldi	BCDb,1		;set underflow carry
	brcc	sub_2		;if carry not clear
	ldi	BCDb,1		;    clear underflow carry	
sub_2:	ret			



;****************************************************************************
;*
;* Test Program
;*
;* This program calls all the subroutines as an example of usage and to 
;* verify correct operation.
;*
;****************************************************************************

;***** Main Program Register variables

.def	temp	=r16		;temporary storage variable

;***** Code

RESET:
	ldi	temp,low(RAMEND)
	out	SPL,temp
	ldi	temp,high(RAMEND)
	out	SPH,temp	;init Stack Pointer (remove for AT90Sxx0x)

;***** Convert 54,321 to 2.5-byte packed BCD format

	ldi	fbinL,low(54321)
	ldi	fbinH,high(54321)
	rcall	bin2BCD16	;result: tBCD2:tBCD1:tBCD0 = $054321

;***** Convert 55 to 2-byte BCD 

	ldi	fbin,55
	rcall	bin2BCD8	;result: tBCDH:tBCDL = 0505

;***** Convert $065535 to a 16-bit binary number
	ldi	fBCD2,$06
	ldi	fBCD1,$55
	ldi	fBCD0,$35
	rcall	BCD2bin16	;result: tbinH:tbinL = $ffff (65,535)

;***** Convert $0403 (43) to an 8-bit binary number
	ldi	fBCDL,3
	ldi	fBCDH,4
	rcall	BCD2bin8	;result: tbin = $2b (43)

;***** Add BCD numbers 51 and 79
	ldi	BCD1,$51
	ldi	BCD2,$79
	rcall	BCDadd		;result: BCD2:BCD1=$0130

;***** Subtract BCD numbers 72 - 28
	ldi	BCDa,$72
	ldi	BCDb,$28
	rcall	BCDsub		;result: BCDb=$00 (positive result), BCDa=44

;***** Subtract BCD numbers 0 - 90
	ldi	BCDa,$00
	ldi	BCDb,$90
	rcall	BCDsub		;result: BCDb=$01 (negative result), BCDa=10	



forever:rjmp	forever


